#include "stdafx.h"
#include "Paint.h"
#include "Claxa.h"
#include "icache/cache.h"
#include "imglib/merge.h"
#include "imglib/alpha_raster.h"
#include "imglib/dichromatic_raster.h"

PaintingKey::PaintingKey()
{
}

bool PaintingKey::equal(const CacheKey & i) const
{
	if (m_Style.Type == ObjectOnly) {
		if (m_pObjectFootage->frame_id(m_ObjectFrame)->equal(i))
			return true;
	}

	if (m_Style.Type == BackgroundOnly) {
		if (m_pBackgroundFootage->frame_id(m_BackgroundFrame)->equal(i))
			return true;
	}

	const PaintingKey * rhs = dynamic_cast<const PaintingKey *>(&i);
	if (rhs == 0)
		return false;

	if (m_Style != rhs->m_Style)
		return false;

	if ((m_Style.Type == ObjectOnly || 
		 m_Style.Type == MattedObject ||
		 m_Style.Type == Composition)
		&&
		(m_pObjectFootage != rhs->m_pObjectFootage ||
		 m_ObjectFrame != rhs->m_ObjectFrame))
		return false;

	if ((m_Style.Type == BackgroundOnly ||
		 m_Style.Type == Composition)
		&&
		(m_pBackgroundFootage != rhs->m_pBackgroundFootage ||
		 m_BackgroundFrame != rhs->m_BackgroundFrame))
		return false;

	if ((m_Style.Type == MaskOnly ||
		 m_Style.Type == MattedObject ||
		 m_Style.Type == Composition)
		&&
		m_MasksWithCombMode != rhs->m_MasksWithCombMode)
		return false;

	return true;
}

String PaintingKey::name() const
{
	assert(!"not implemented");
	return String();
}

void PaintingKey::serialize(std::ostream & o) const
{
	assert(!"not implemented");
}

void PaintingKey::request() const
{
	assert(!"n/a");
}

void PaintingKey::load(ByteImage & im) const
{
	Merge(im, icache::no_nearby);
}

class Merger
{
public:
	Merger(ByteImage & i, bool inv) : im(i), InverseAlpha(inv) { }
	template <class R>
	void operator () (R const & r) {
		if (InverseAlpha)
			img::transform(im, r, img::inv_alpha_blend<ByteColor, false>());
		else
			img::transform(im, r, img::alpha_blend<ByteColor, true>());
	}
private:
	ByteImage & im;
	bool InverseAlpha;
};

struct MaskToGreyColor {
	typedef ByteColor result_type;
	ByteColor operator () (unsigned char m) {
		return ByteColor(m, m, m, 255-m);
	}
};

struct MaskToInvGreyColor {
	typedef ByteColor result_type;
	ByteColor operator () (unsigned char m) {
		return ByteColor(255-m, 255-m, 255-m, m);
	}
};

void BitMaskImage(
	ByteImage & im, ///< preresized
	const BitMask & Mask, const size_point & From,
	DisplayStyle const & d)
{
	ByteColor val0(0,0,0,255), val1(255,255,255,0);
	if (d.InverseMask)
		std::swap(val0, val1);
	img::copy(im, make_dichromatic_raster(Mask, val0, val1), From);
}

inline void BitMaskImage(ByteImage & im, BitMask const & m, DisplayStyle const & d)
{
	BitMaskImage(im, m, size_point(0, 0), d);
}

void BlendWithBitMask(
	ByteImage & im, ///< preresized
	/*const ByteImage & Src,*/ const BitMask & Mask, const size_point & From,
	DisplayStyle const & d)
{
	unsigned char val0, val1;
	if (d.InverseMask) {
		val0 = d.MatteColor.a;
		val1 = 0;
	}
	else {
		val0 = 0;
		val1 = d.MatteColor.a;
	}

	ByteImage::iterator i;
	ByteColor b = d.MatteColor;
	for (i = im.begin(); i != im.end(); ++i) {
		b.a = Mask[i + From] ? val1 : val0;
		img::alpha_blend<ByteColor, true>()(*i, b);
	}
}

inline void BlendWithBitMask(ByteImage & im, BitMask const & m, DisplayStyle const & d)
{
	BlendWithBitMask(im, m, size_point(0, 0), d);
}

void PaintingKey::Merge(ByteImage & im, icache::nearby nb) const
{
	///this function should be used only for compositing,
	///to retrieve original images call cache directly
	assert(m_Style.Type != ObjectOnly &&
		   m_Style.Type != BackgroundOnly);

	BitMask bm;
	GreyMask gm;
	if (!m_MasksWithCombMode.empty()) {
		if (m_Style.SmoothMask)
			MergeMasks(gm, m_MasksWithCombMode);
		else
			MergeMasks(bm, m_MasksWithCombMode);
	}

	if (m_Style.Type == MaskOnly) {
		im.resize(m_pObjectFootage->dim());
		if (m_MasksWithCombMode.empty()) {
			img::fill(im,
				m_Style.InverseMask ? ByteColor(255,255,255,0) : ByteColor(0,0,0,255));
		}
		else if (!m_Style.SmoothMask)
			BitMaskImage(im, bm, m_Style);
		else {
			if (m_Style.InverseMask)
				im = modify(gm, MaskToInvGreyColor());
			else
				im = modify(gm, MaskToGreyColor());
		}
		return;
	}

	//simultanius read of two frames from overlay and underlay;
	CachedImageSPtr o, b;
	o = m_pObjectFootage->get(m_ObjectFrame, nb, false); // if the frame is not in the cache, then it starts loading
	if (m_Style.Type == Composition)
		b = m_pBackgroundFootage->get(m_BackgroundFrame, nb);
	if (!o)
		o = m_pObjectFootage->get(m_ObjectFrame, nb);

	im = o->im;

	if (m_Style.Type == Composition) {
		assert(b);
		Merger mr(im, m_Style.InverseMask);
		//compose object with background according to the mask
		if (!m_Style.SmoothMask)
			mr(img::make_alpha_raster(b->im, 
				img::dichromatic_raster<unsigned char>(bm, 0, 255)));
		else
			mr(img::make_alpha_raster(b->im, gm));
		return;
	}

	assert(m_Style.Type == MattedObject);

	//blend object with matte color according to the mask
	if (!m_Style.SmoothMask) {
		BlendWithBitMask(im, bm, m_Style);
	}
	else {
		img::transform(im, 
			img::make_alpha_product_raster(
				make_uniform_raster(m_Style.MatteColor, im.dim()), gm, m_Style.InverseMask),
			img::alpha_blend<ByteColor, true>());
	}
}

CachedImageSPtr GetPainting(PaintingKeySPtr const & p, icache::nearby nb, bool RAMPreview)
{
	CachedImageSPtr r = theApp.m_Cache.coordinator().try_get(p);
	if (r)
		return r;

	if (p->m_Style.Type == ObjectOnly)
		return p->m_pObjectFootage->get(p->m_ObjectFrame, nb);

	if (p->m_Style.Type == BackgroundOnly)
		return p->m_pBackgroundFootage->get(p->m_BackgroundFrame, nb);

	r = CachedImage::create(p);
	p->Merge(r->im, nb);
	if (RAMPreview)
		theApp.m_Cache.coordinator().put(r);

	return r;
}
